import { EventEmitter } from 'events';
import Redis from 'ioredis';
import { config } from '../config/serviceConfig';
import { serviceFactory } from './serviceFactory';
import { errorHandler, AppError } from '../utils/errorHandler';
import os from 'os';

export interface MetricData {
  name: string;
  value: number;
  tags?: Record<string, string>;
  timestamp?: number;
}

export interface HealthStatus {
  redis: boolean;
  services: Record<string, boolean>;
  overallStatus: 'healthy' | 'degraded' | 'unhealthy';
  timestamp: number;
  details?: {
    memory: {
      total: number;
      free: number;
      usage: NodeJS.MemoryUsage;
    };
    cpu: {
      load1: number;
      load5: number;
      load15: number;
      cores: number;
    };
    uptime: {
      system: number;
      process: number;
    };
    network: {
      activeConnections: number;
      interfaces: NodeJS.Dict<os.NetworkInterfaceInfo[]>;
    };
    lastErrors: unknown[];
  };
}

export class MonitoringService extends EventEmitter {
  private static instance: MonitoringService;
  private metricsBuffer: MetricData[] = [];
  private flushInterval?: NodeJS.Timeout;
  private healthCheckInterval?: NodeJS.Timeout;
  private readonly redis: Redis;
  private lastHealthStatus: HealthStatus;
  private isShuttingDown = false;

  private constructor() {
    super();
    this.redis = serviceFactory.getRedis();
    this.lastHealthStatus = this.createInitialHealthStatus();
    this.setupMetricsCollection();
    this.setupHealthChecks();
    this.setupEventHandlers();
  }

  static getInstance(): MonitoringService {
    if (!MonitoringService.instance) {
      MonitoringService.instance = new MonitoringService();
    }
    return MonitoringService.instance;
  }

  recordMetric(data: MetricData): void {
    try {
      if (this.isShuttingDown) {
        throw new AppError('Service is shutting down', 'SERVICE_SHUTDOWN');
      }

      const metric = {
        ...data,
        timestamp: data.timestamp || Date.now(),
        tags: {
          env: process.env.NODE_ENV || 'development',
          host: os.hostname(),
          ...data.tags
        }
      };

      this.metricsBuffer.push(metric);
      this.emit('metric', metric);

      if (this.metricsBuffer.length >= config.performance.metrics.bufferSize) {
        void this.flushMetrics();
      }
    } catch (error) {
      errorHandler.handleError(error instanceof Error ? error : new Error('Failed to record metric'));
    }
  }

  async getHealthStatus(): Promise<HealthStatus> {
    try {
      if (this.isShuttingDown) {
        throw new AppError('Service is shutting down', 'SERVICE_SHUTDOWN');
      }

      const [redisHealth, serviceHealth] = await Promise.all([
        this.checkRedisHealth(),
        this.checkServicesHealth()
      ]);

      this.lastHealthStatus = {
        redis: redisHealth,
        services: serviceHealth,
        overallStatus: this.calculateOverallStatus(redisHealth, serviceHealth),
        timestamp: Date.now(),
        details: await this.gatherHealthDetails()
      };

      this.emit('health_status', this.lastHealthStatus);
      return this.lastHealthStatus;
    } catch (error) {
      errorHandler.handleError(error instanceof Error ? error : new Error('Health check failed'));
      return this.lastHealthStatus;
    }
  }

  async stop(): Promise<void> {
    try {
      this.isShuttingDown = true;
      this.emit('stopping');

      if (this.flushInterval) {
        clearInterval(this.flushInterval);
        this.flushInterval = undefined;
      }

      if (this.healthCheckInterval) {
        clearInterval(this.healthCheckInterval);
        this.healthCheckInterval = undefined;
      }

      await this.flushMetrics();
      await new Promise(resolve => setImmediate(resolve));
      
      this.emit('stopped');
    } catch (error) {
      errorHandler.handleError(error instanceof Error ? error : new Error('Stop failed'));
    }
  }

  private createInitialHealthStatus(): HealthStatus {
    return {
      redis: false,
      services: {},
      overallStatus: 'unhealthy',
      timestamp: Date.now()
    };
  }

  private setupMetricsCollection(): void {
    const interval = Math.max(config.performance.metrics.collectInterval, 1000);
    this.flushInterval = setInterval(() => { void this.flushMetrics(); }, interval);
  }

  private setupHealthChecks(): void {
    const interval = Math.max(config.monitor.alerts.checkInterval, 1000);
    this.healthCheckInterval = setInterval(() => { 
      void this.getHealthStatus().catch(error => {
        errorHandler.handleError(error instanceof Error ? error : new Error('Health check failed'));
      });
    }, interval);
  }

  private setupEventHandlers(): void {
    this.on('error', (error: Error) => {
      errorHandler.handleError(error);
    });

    process.on('uncaughtException', (error: Error) => {
      this.emit('error', error);
    });

    process.on('unhandledRejection', (reason: unknown) => {
      this.emit('error', reason instanceof Error ? reason : new Error(String(reason)));
    });
  }

  private async flushMetrics(): Promise<void> {
    if (this.metricsBuffer.length === 0) return;

    try {
      const metrics = [...this.metricsBuffer];
      this.metricsBuffer = [];

      const pipeline = this.redis.pipeline();
      
      for (const metric of metrics) {
        const key = `metrics:${metric.name}:${this.formatDate(metric.timestamp)}`;
        const timestamp = Math.floor(metric.timestamp || Date.now());
        const record = {
          value: metric.value,
          tags: metric.tags || {},
          timestamp
        };

        pipeline
          .zadd(key, timestamp, JSON.stringify(record))
          .expire(key, config.performance.metrics.retention);
      }

      await pipeline.exec();
      this.emit('metrics_flushed', metrics.length);
    } catch (error) {
      errorHandler.handleError(error instanceof Error ? error : new Error('Failed to flush metrics'));
      // 恢复未成功写入的指标
      this.metricsBuffer = [...this.metricsBuffer];
    }
  }

  private async checkRedisHealth(): Promise<boolean> {
    try {
      await this.redis.ping();
      return true;
    } catch {
      return false;
    }
  }

  private async checkServicesHealth(): Promise<Record<string, boolean>> {
    try {
      return await serviceFactory.healthCheck();
    } catch (error) {
      errorHandler.handleError(error instanceof Error ? error : new Error('Service health check failed'));
      return {};
    }
  }

  private calculateOverallStatus(
    redisHealth: boolean,
    serviceHealth: Record<string, boolean>
  ): HealthStatus['overallStatus'] {
    if (!redisHealth) return 'unhealthy';

    const serviceStatuses = Object.values(serviceHealth);
    if (serviceStatuses.length === 0) return 'unhealthy';

    const healthyServices = serviceStatuses.filter(status => status).length;
    const errorRate = (serviceStatuses.length - healthyServices) / serviceStatuses.length * 100;

    if (errorRate === 0) return 'healthy';
    if (errorRate >= config.monitor.alerts.errorThreshold) return 'unhealthy';
    return 'degraded';
  }

  private async gatherHealthDetails(): Promise<HealthStatus['details']> {
    const loadAvg = os.loadavg();
    return {
      memory: {
        total: os.totalmem(),
        free: os.freemem(),
        usage: process.memoryUsage()
      },
      cpu: {
        load1: loadAvg[0],
        load5: loadAvg[1],
        load15: loadAvg[2],
        cores: os.cpus().length
      },
      uptime: {
        system: os.uptime(),
        process: process.uptime()
      },
      network: await this.getNetworkStats(),
      lastErrors: await this.getRecentErrors()
    };
  }

  private async getNetworkStats(): Promise<HealthStatus['details']['network']> {
    try {
      const activeConnections = await this.getActiveConnections();
      return {
        activeConnections,
        interfaces: os.networkInterfaces()
      };
    } catch (error) {
      errorHandler.handleError(error instanceof Error ? error : new Error('Failed to gather network stats'));
      return {
        activeConnections: 0,
        interfaces: {}
      };
    }
  }

  private async getActiveConnections(): Promise<number> {
    try {
      const connectionsKey = 'monitoring:active_connections';
      const count = await this.redis.get(connectionsKey);
      return parseInt(count || '0', 10);
    } catch {
      return 0;
    }
  }

  private async getRecentErrors(): Promise<unknown[]> {
    try {
      const errorsKey = 'monitoring:recent_errors';
      const errors = await this.redis.lrange(errorsKey, 0, config.monitor.errorLog.maxSize - 1);
      return errors.map(error => {
        try {
          return JSON.parse(error);
        } catch {
          return { error: 'Invalid error format', raw: error };
        }
      });
    } catch {
      return [];
    }
  }

  private formatDate(timestamp?: number): string {
    return new Date(timestamp || Date.now()).toISOString().split('T')[0];
  }
}

export const monitoringService = MonitoringService.getInstance();

process.once('SIGTERM', () => {
  void monitoringService.stop();
});

process.once('SIGINT', () => {
  void monitoringService.stop();
});
